﻿using mozhi.smarterp.AuthorCenter.Enums;
using mozhi.smarterp.AuthorCenter.Identities.Clients;
using OpenIddict.Abstractions;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Volo.Abp;
using Volo.Abp.Application.Dtos;
using Volo.Abp.OpenIddict.Applications;
using Volo.Abp.OpenIddict.Scopes;
using Volo.Abp.Users;

namespace mozhi.smarterp.AuthorCenter.Identities
{
    public class OpenIddictClientAppService : AuthorCenterAppService, IOpenIddictClientAppService
    {
        private readonly IAbpApplicationManager _applicationManager;
        private readonly IOpenIddictScopeManager _scopeManager;

        public OpenIddictClientAppService(IAbpApplicationManager applicationManager , IOpenIddictScopeManager scopeManager)
        {
            _applicationManager = applicationManager;
            _scopeManager = scopeManager;
        }


        public async Task<OpenIddictClientDto> GetAsync(string name)
        {
            var client = (await _applicationManager.FindByClientIdAsync(name)) as OpenIddictApplicationModel;

            var item = MapTo(client);

            return item;
        }

        public async Task<PagedResultDto<OpenIddictClientDto>> GetListAsync(PagedResultRequestDto filter)
        {
            var count = await _applicationManager.CountAsync();

            var list= new List<OpenIddictClientDto>();

            await foreach (OpenIddictApplicationModel client in _applicationManager.ListAsync(filter.MaxResultCount, filter.SkipCount))
            {
                var item = MapTo(client);

                if (item != null)
                    list.Add(item);
            }

            return new PagedResultDto<OpenIddictClientDto>() { Items = list, TotalCount = count };

        }


        public async Task<OpenIddictClientDto> CreateAsync(OpenIddictClientCreateDto input)
        {
            var client = await _applicationManager.FindByClientIdAsync(input.Name);

            if (client != null)
                throw new UserFriendlyException($"client:{input.Name}已存在");

            var application = await GetApplicationAsync(input.Name, input.Type, input.ConsentType, input.DisplayName, input.Secret, input.GrantTypes, input.Scopes, input.ClientUri, input.RedirectUri, input.PostLogoutRedirectUri);

            application = await _applicationManager.CreateAsync(application) as AbpApplicationDescriptor;

            var result = MapTo(application);

            return result;

        }


        public async Task<OpenIddictClientDto> UpdateAsync(OpenIddictClientUpdateDto input)
        {
            var client = (await _applicationManager.FindByClientIdAsync(input.Name)) as OpenIddictApplicationModel;

            if (client == null)
                throw new UserFriendlyException($"client:{input.Name}不存在");

            client.DisplayName = input.DisplayName;
            client.ConsentType = input.ConsentType;
            client.Type = input.Type;
            
            client.RedirectUris = $"[\"{input.RedirectUris?.JoinAsString("\",\"")}\"]";
            client.PostLogoutRedirectUris = $"[\"{input.PostLogoutRedirectUris?.JoinAsString("\",\"")}\"]";
            client.Permissions = $"[\"{input.Permissions?.JoinAsString("\",\"")}\"]";

            await _applicationManager.UpdateAsync(client);

            return MapTo(client);
        }

        public async Task DeleteAsync(string name)
        {
            var client = await _applicationManager.FindByClientIdAsync(name);

            if (client == null)
                throw new UserFriendlyException($"client:{name}不存在");

            await _applicationManager.DeleteAsync(client);
        }


        public async Task<List<OpenIddictClientPermissionItemDto>> GetClientPermissionAsync(ClientPermissionType? type)
        {
            var result = new List<OpenIddictClientPermissionItemDto>();

            //endpoint
            result.Add(new OpenIddictClientPermissionItemDto(nameof(OpenIddictConstants.Permissions.Endpoints.Authorization), OpenIddictConstants.Permissions.Endpoints.Authorization, ClientPermissionType.Endpoint));
            result.Add(new OpenIddictClientPermissionItemDto(nameof(OpenIddictConstants.Permissions.Endpoints.Logout), OpenIddictConstants.Permissions.Endpoints.Logout, ClientPermissionType.Endpoint));
            result.Add(new OpenIddictClientPermissionItemDto(nameof(OpenIddictConstants.Permissions.Endpoints.Token), OpenIddictConstants.Permissions.Endpoints.Token, ClientPermissionType.Endpoint));

            result.Add(new OpenIddictClientPermissionItemDto(nameof(OpenIddictConstants.Permissions.GrantTypes.AuthorizationCode), OpenIddictConstants.Permissions.GrantTypes.AuthorizationCode, ClientPermissionType.GrantType));
            result.Add(new OpenIddictClientPermissionItemDto(nameof(OpenIddictConstants.Permissions.GrantTypes.Implicit), OpenIddictConstants.Permissions.GrantTypes.Implicit, ClientPermissionType.GrantType));
            result.Add(new OpenIddictClientPermissionItemDto(nameof(OpenIddictConstants.Permissions.GrantTypes.Password), OpenIddictConstants.Permissions.GrantTypes.Password, ClientPermissionType.GrantType));

            result.Add(new OpenIddictClientPermissionItemDto(nameof(OpenIddictConstants.Permissions.Scopes.Address), OpenIddictConstants.Permissions.Scopes.Address, ClientPermissionType.Scope));
            result.Add(new OpenIddictClientPermissionItemDto(nameof(OpenIddictConstants.Permissions.Scopes.Email), OpenIddictConstants.Permissions.Scopes.Email, ClientPermissionType.Scope));
            result.Add(new OpenIddictClientPermissionItemDto(nameof(OpenIddictConstants.Permissions.Scopes.Phone), OpenIddictConstants.Permissions.Scopes.Phone, ClientPermissionType.Scope));
            result.Add(new OpenIddictClientPermissionItemDto(nameof(OpenIddictConstants.Permissions.Scopes.Profile), OpenIddictConstants.Permissions.Scopes.Profile, ClientPermissionType.Scope));
            result.Add(new OpenIddictClientPermissionItemDto(nameof(OpenIddictConstants.Permissions.Scopes.Roles), OpenIddictConstants.Permissions.Scopes.Roles, ClientPermissionType.Scope));
            result.Add(new OpenIddictClientPermissionItemDto("DepartmentId", "scp:departmentId" , ClientPermissionType.Scope));
            result.Add(new OpenIddictClientPermissionItemDto("DataPermissionId", "scp:dataPermissionId", ClientPermissionType.Scope));

            await foreach(OpenIddictScopeModel apiScope in  _scopeManager.ListAsync(int.MaxValue, 0))
            {
                result.Add(new OpenIddictClientPermissionItemDto(apiScope.Name, $"scp:{apiScope.Name}", ClientPermissionType.Scope));
            }



            result.Add(new OpenIddictClientPermissionItemDto(nameof(OpenIddictConstants.Permissions.ResponseTypes.Code), OpenIddictConstants.Permissions.ResponseTypes.Code, ClientPermissionType.ResponseType));
            result.Add(new OpenIddictClientPermissionItemDto(nameof(OpenIddictConstants.Permissions.ResponseTypes.CodeIdToken), OpenIddictConstants.Permissions.ResponseTypes.CodeIdToken, ClientPermissionType.ResponseType));
            result.Add(new OpenIddictClientPermissionItemDto(nameof(OpenIddictConstants.Permissions.ResponseTypes.CodeIdTokenToken), OpenIddictConstants.Permissions.ResponseTypes.CodeIdTokenToken, ClientPermissionType.ResponseType));
            result.Add(new OpenIddictClientPermissionItemDto(nameof(OpenIddictConstants.Permissions.ResponseTypes.CodeToken), OpenIddictConstants.Permissions.ResponseTypes.CodeToken, ClientPermissionType.ResponseType));
            result.Add(new OpenIddictClientPermissionItemDto(nameof(OpenIddictConstants.Permissions.ResponseTypes.IdToken), OpenIddictConstants.Permissions.ResponseTypes.IdToken, ClientPermissionType.ResponseType));
            result.Add(new OpenIddictClientPermissionItemDto(nameof(OpenIddictConstants.Permissions.ResponseTypes.IdTokenToken), OpenIddictConstants.Permissions.ResponseTypes.IdTokenToken, ClientPermissionType.ResponseType));
            result.Add(new OpenIddictClientPermissionItemDto(nameof(OpenIddictConstants.Permissions.ResponseTypes.None), OpenIddictConstants.Permissions.ResponseTypes.None, ClientPermissionType.ResponseType));
            result.Add(new OpenIddictClientPermissionItemDto(nameof(OpenIddictConstants.Permissions.ResponseTypes.Token), OpenIddictConstants.Permissions.ResponseTypes.Token, ClientPermissionType.ResponseType));

            if (type.HasValue)
                result = result.Where(x => x.Type == type).ToList();

            return result;
        }

        #region
        private async Task<AbpApplicationDescriptor> GetApplicationAsync(string name, string type, string consentType, string displayName, string secret, List<string> grantTypes, List<string> scopes,
            string clientUri = null, string redirectUri = null, string postLogoutRedirectUri = null)
        {
            if (!string.IsNullOrEmpty(secret) && string.Equals(type, OpenIddictConstants.ClientTypes.Public, StringComparison.OrdinalIgnoreCase))
                throw new UserFriendlyException("公开的客户端不能设置密码");

            if (string.IsNullOrEmpty(secret) && string.Equals(type, OpenIddictConstants.ClientTypes.Confidential, StringComparison.OrdinalIgnoreCase))
                throw new UserFriendlyException("保密的客户端必须设置密码");

            if (string.IsNullOrEmpty(name))
                throw new UserFriendlyException("客户端名称为必填项");

            var client = await _applicationManager.FindByClientIdAsync(name);

            AbpApplicationDescriptor application;

            if (client == null)
                application = new AbpApplicationDescriptor()
                {
                    ClientId = name,
                    Type = type,
                    ClientSecret = secret,
                    ConsentType = consentType,
                    DisplayName = displayName,
                    ClientUri = clientUri
                };
            else
            {
                var existClient = client as OpenIddictApplicationModel;

                application = new AbpApplicationDescriptor()
                {
                    ClientId = existClient.ClientId,
                    Type = type,
                    ClientSecret = secret,
                    ConsentType = consentType,
                    DisplayName = displayName,
                    ClientUri = clientUri
                };
            }

            application.Permissions.Clear();

            Check.NotNullOrEmpty(grantTypes, nameof(grantTypes));
            Check.NotNullOrEmpty(scopes, nameof(scopes));

            if (new[] { OpenIddictConstants.GrantTypes.AuthorizationCode, OpenIddictConstants.GrantTypes.Implicit }.All(grantTypes.Contains))
            {
                application.Permissions.Add(OpenIddictConstants.Permissions.ResponseTypes.CodeIdToken);

                if (string.Equals(type, OpenIddictConstants.ClientTypes.Public, StringComparison.OrdinalIgnoreCase))
                {
                    application.Permissions.Add(OpenIddictConstants.Permissions.ResponseTypes.CodeIdTokenToken);
                    application.Permissions.Add(OpenIddictConstants.Permissions.ResponseTypes.CodeToken);
                }
            }

            if (!redirectUri.IsNullOrWhiteSpace() || !postLogoutRedirectUri.IsNullOrWhiteSpace())
            {
                application.Permissions.Add(OpenIddictConstants.Permissions.Endpoints.Logout);
            }

            foreach (var grantType in grantTypes)
            {
                if (grantType == OpenIddictConstants.GrantTypes.AuthorizationCode)
                {
                    application.Permissions.Add(OpenIddictConstants.Permissions.GrantTypes.AuthorizationCode);
                    application.Permissions.Add(OpenIddictConstants.Permissions.ResponseTypes.Code);
                }

                if (grantType == OpenIddictConstants.GrantTypes.AuthorizationCode || grantType == OpenIddictConstants.GrantTypes.Implicit)
                {
                    application.Permissions.Add(OpenIddictConstants.Permissions.Endpoints.Authorization);
                }

                if (grantType == OpenIddictConstants.GrantTypes.AuthorizationCode ||
                    grantType == OpenIddictConstants.GrantTypes.ClientCredentials ||
                    grantType == OpenIddictConstants.GrantTypes.Password ||
                    grantType == OpenIddictConstants.GrantTypes.RefreshToken ||
                    grantType == OpenIddictConstants.GrantTypes.DeviceCode)
                {
                    application.Permissions.Add(OpenIddictConstants.Permissions.Endpoints.Token);
                    application.Permissions.Add(OpenIddictConstants.Permissions.Endpoints.Revocation);
                    application.Permissions.Add(OpenIddictConstants.Permissions.Endpoints.Introspection);
                }

                if (grantType == OpenIddictConstants.GrantTypes.ClientCredentials)
                {
                    application.Permissions.Add(OpenIddictConstants.Permissions.GrantTypes.ClientCredentials);
                }

                if (grantType == OpenIddictConstants.GrantTypes.Implicit)
                {
                    application.Permissions.Add(OpenIddictConstants.Permissions.GrantTypes.Implicit);
                }

                if (grantType == OpenIddictConstants.GrantTypes.Password)
                {
                    application.Permissions.Add(OpenIddictConstants.Permissions.GrantTypes.Password);
                }

                if (grantType == OpenIddictConstants.GrantTypes.RefreshToken)
                {
                    application.Permissions.Add(OpenIddictConstants.Permissions.GrantTypes.RefreshToken);
                }

                if (grantType == OpenIddictConstants.GrantTypes.DeviceCode)
                {
                    application.Permissions.Add(OpenIddictConstants.Permissions.GrantTypes.DeviceCode);
                    application.Permissions.Add(OpenIddictConstants.Permissions.Endpoints.Device);
                }

                if (grantType == OpenIddictConstants.GrantTypes.Implicit)
                {
                    application.Permissions.Add(OpenIddictConstants.Permissions.ResponseTypes.IdToken);
                    if (string.Equals(type, OpenIddictConstants.ClientTypes.Public, StringComparison.OrdinalIgnoreCase))
                    {
                        application.Permissions.Add(OpenIddictConstants.Permissions.ResponseTypes.IdTokenToken);
                        application.Permissions.Add(OpenIddictConstants.Permissions.ResponseTypes.Token);
                    }
                }
            }

            var buildInScopes = new[]
            {
                    OpenIddictConstants.Permissions.Scopes.Address,
                    OpenIddictConstants.Permissions.Scopes.Email,
                    OpenIddictConstants.Permissions.Scopes.Phone,
                    OpenIddictConstants.Permissions.Scopes.Profile,
                    OpenIddictConstants.Permissions.Scopes.Roles
            };

            foreach (var scope in scopes)
            {
                if (buildInScopes.Contains(scope))
                {
                    application.Permissions.Add(scope);
                }
                else
                {
                    application.Permissions.Add(OpenIddictConstants.Permissions.Prefixes.Scope + scope);
                }
            }

            if (redirectUri != null)
            {
                if (!redirectUri.IsNullOrEmpty())
                {
                    if (!Uri.TryCreate(redirectUri, UriKind.Absolute, out var uri) || !uri.IsWellFormedOriginalString())
                    {
                        throw new BusinessException($"非法的地址{redirectUri}");
                    }

                    if (application.RedirectUris.All(x => x != uri))
                    {
                        application.RedirectUris.Add(uri);
                    }
                }
            }

            if (postLogoutRedirectUri != null)
            {
                if (!postLogoutRedirectUri.IsNullOrEmpty())
                {
                    if (!Uri.TryCreate(postLogoutRedirectUri, UriKind.Absolute, out var uri) || !uri.IsWellFormedOriginalString())
                    {
                        throw new BusinessException(L["InvalidPostLogoutRedirectUri", postLogoutRedirectUri]);
                    }

                    if (application.PostLogoutRedirectUris.All(x => x != uri))
                    {
                        application.PostLogoutRedirectUris.Add(uri);
                    }
                }
            }

            return application;
        }

        private OpenIddictClientDto MapTo(OpenIddictApplicationModel source)
        {
            if (source == null)
                return null;

            var target = new OpenIddictClientDto();

            target.Id = source.Id;
            target.Name = source.ClientId;
            target.Type = source.Type;
            target.ConsentType = source.ConsentType;
            target.DisplayName = source.DisplayName;
            target.ClientUri = source.ClientUri;
            target.PostLogoutRedirectUris = source.PostLogoutRedirectUris?.Replace("[", "").Replace("]", "").Replace("\"","").Split(",").ToList();

            target.RedirectUris = source.RedirectUris?.Replace("[", "").Replace("]", "").Replace("\"", "").Split(",").ToList();

            target.Permissions = source.Permissions?.Replace("[", "").Replace("]", "").Replace("\"", "").Split(",").ToList();

            return target;
        }

        private OpenIddictClientDto MapTo(AbpApplicationDescriptor source)
        {
            if (source == null)
                return null;

            var target = new OpenIddictClientDto();

            target.Name = source.ClientId;
            target.Type = source.Type;
            target.ConsentType = source.ConsentType;
            target.DisplayName = source.DisplayName;
            target.Secret = source.ClientSecret;

            target.ClientUri = source.ClientUri;
            target.RedirectUris = source.RedirectUris?.Select(x => x.AbsoluteUri).ToList();
            target.PostLogoutRedirectUris = source.PostLogoutRedirectUris?.Select(x => x.AbsoluteUri).ToList();
            target.Permissions = source.Permissions?.ToList();

            return target;
        }
        #endregion
    }
}
